#include "chatroom.h"

int UserNumAlloc(void);
void MsgTransmit(int type, int UserNum, Message Msg);
void ClientListUpdate(int cmd, char *UserName, int UserNum);
void *MsgRecv(void *arg_t);
void *MsgSend(void *fd);

int main(void)
{
    int fd, clientfd, wr_fifo;
    socklen_t sock_len;
    sock_len = sizeof(struct sockaddr_in);

    printf("\e[33m[Daemon] Starting the server...\e[0m\n");

    pthread_t tid1; //, tid2;
    mkfifo("FIFO", O_CREAT | O_EXCL);   //建立管道，方便线程间通讯
    pthread_create(&tid1, NULL, MsgSend, NULL); //为MsgSend函数创建一个线程

    //初始化socket端口的配置信息
    struct sockaddr_in Server, Client;
    Server.sin_family = AF_INET;
    Server.sin_port = htons(PORT);
    Server.sin_addr.s_addr = INADDR_ANY;

    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)   //尝试建立socket，并保存套接字
    {
        perror("Socket init failed!\n");
        exit(SOCKET_INIT_ERR);
    }
    if (bind(fd, (struct sockaddr*)&Server, sock_len) == -1)    //将socket绑定
    {
        perror("Socket bind failed!\n");
        exit(SOCKET_BIND_ERR);
    }
    if (listen(fd, MAX_CLIENTS + 1) == -1)  //开始监听socket
    {
        perror("Socket listen failed!\n");
        exit(SOCKET_LISTEN_ERR);
    }
    if ((wr_fifo = open("FIFO", O_WRONLY)) == -1)   //打开管道，这个命令需要超级用户权限
    {
        perror("Fifo open failed!\n");
        exit(FIFO_OPEN_ERR);
    }

    while (1)
    {
        if ((clientfd = (accept(fd, (struct sockaddr*)&Client, &sock_len))) == -1)  //收到客户端连接请求，建立连接
        {
            perror("Socket accept failed!\n");
            exit(SOCKET_ACCEPT_ERR);
        }

        int UserNum = -1;
        Message Msg;
        if ((UserNum = UserNumAlloc()) == -1)   //为用户分配用户号码
        {
            printf("\e[33m[ERROR] Too many clients!\e[0m\n");
            Msg.MsgType = LOGOUT;
            write(clientfd, &Msg, sizeof(Msg)); //用户太多，拒绝客户端的连接
        }
        else
        {
            struct ARG arg;
            arg.fifofd = wr_fifo;
            arg.UserNum = UserNum;
            Msg.MsgType = OK;
            CList[UserNum].socketfd = clientfd;
            memcpy(&Msg.CList, &CList, sizeof(CList));
            Msg.SourceNum = UserNum;
            write(clientfd, &Msg, sizeof(Msg)); //将连接成功的信息发送到客户端，并同时发送分配的用户号码和在线用户列表
            pthread_create(&tid1, NULL, MsgRecv, (void *)&arg); //创建一个线程用于接受该客户端的消息
        }
    }
    
    pthread_join(tid1, NULL);
    //pthread_join(tid2, NULL);


}

int UserNumAlloc(void)  //分配用户号码
{
    int i;
    for (i = 0; i < MAX_CLIENTS; i++)   //遍历用户列表
    {
        if (CList[i].socketfd == 0) //找到了没有被占用的用户就跳出
            break;
    }
    if (i < MAX_CLIENTS)
        return i;   //返回找到的空闲的用户位
    else
        return -1;  //用户已满，拒绝连接
}

void MsgTransmit(int type, int UserNum, Message Msg)    //似乎可以优化一下传递的参数，但是有的时候会有bug？
{   //传输信息函数
    memcpy(&Msg.CList, &CList, sizeof(CList));  //更新在线列表
    if (type == PRIVATE)    //信息类型为私聊消息
    {
        write(CList[Msg.TargetNum].socketfd, &Msg, sizeof(Msg));    //发送消息给目标客户端
        printf("\e[31m[Private] Message (From: %-10s To: %-10s): %s\e[0m\n", CList[UserNum].name, CList[Msg.TargetNum].name, Msg.MsgContent);
    }
    else if (type == PUBLIC)    //信息类型为广播信息
    {
        int i;
        for (i = 0; i < MAX_CLIENTS; i++)
        {   //遍历所有在线的客户端，并将信息发送给除了发送者以外的客户端
            if (CList[i].socketfd != 0 && i != UserNum)
            {
                write(CList[i].socketfd, &Msg, sizeof(Msg));
                printf("\e[32m[Public] Message (From: %-10s): %s\e[0m\n", CList[UserNum].name, Msg.MsgContent);
            }
        }
    }
    else if (type == LOGIN) //信息类型为新用户登录
    {
        write(CList[UserNum].socketfd, &Msg, sizeof(Msg));  //返回登陆成功的消息
        for (int i = 0; i < MAX_CLIENTS; i++)
        {   //遍历所有在线的客户端，并将登陆信息发送给除了登陆者以外的客户端
            if (CList[i].socketfd != 0 && i != UserNum)
            {
                Msg.MsgType = LOGIN;
                write(CList[i].socketfd, &Msg, sizeof(Msg));
            }
        }
    }
    return;
}

void ClientListUpdate(int cmd, char *UserName, int UserNum) //在main中已经更新了sockfd，此处更新其他信息
{   //不会写数据库就这样随便搞搞啦
    if (cmd == ADD) //添加用户
    {
        strcpy(CList[UserNum].name, UserName);  //存储用户名
        printf("\e[33m[USER LOGIN] Name: %-10s\e[0m\n", CList[UserNum].name);
    }
    else if(cmd == DEL) //删除用户
    {
        CList[UserNum].socketfd = 0;    //清空socketfd
        printf("\e[33m[USER LOGOUT] Name: %-10s\e[0m\n", CList[UserNum].name);
        bzero(CList[UserNum].name, NAME_LEN);   //清空用户名
    }
}

void *MsgRecv(void *arg_t)  //接受信息
{
    struct ARG arg = *(struct ARG *)arg_t;
    Message Msg;
    while(1)
    {
        int flag;
        bzero(&Msg, sizeof(Msg));
        Msg.MsgType = ERROR;    //初始化信息内容
        read(CList[arg.UserNum].socketfd, &Msg, sizeof(Msg));   //从客户端接受信息
        Msg.SourceNum = arg.UserNum;
        if (Msg.MsgType == ERROR || Msg.MsgType == LOGOUT)  //如果客户端登出了或者炸了，就终结子进程
        {
            if (Msg.MsgType == ERROR)
            {
                strcpy(Msg.MsgContent, "Mulfunction");
                printf("\e[33m[Mulfunction] Client: %-10s has failed.\e[0m\n", CList[Msg.SourceNum].name);
                Msg.MsgType = LOGOUT;   //准备发送登出信息
            }
            if ((flag = write(arg.fifofd, &Msg, sizeof(Msg))) == -1)
            {
                perror("Fifo write failed!\n");
                exit(FIFO_WRITE_ERR);
            }
            break;
        }
        if ((flag = write(arg.fifofd, &Msg, sizeof(Msg))) == -1)
        {
            perror("Fifo write failed!\n");
            exit(FIFO_WRITE_ERR);
        }
    }
    return NULL;
}

void *MsgSend(void *fd) //发送信息
{
    int fifofd;
    if ((fifofd = open("FIFO", O_RDONLY)) == -1)    //打开管道
    {
        perror("Fifo open failed!\n");
        exit(FIFO_OPEN_ERR);
    }
    int flag;
    Message Msg;
    while(1)
    {
        if ((flag = read(fifofd, &Msg, sizeof(Msg))) == -1)
        {
            perror("Fifo read failed!\n");
            exit(FIFO_READ_ERR);
        }
        int logout_fd;
        switch (Msg.MsgType)    //根据信息类型选择操作
        {
            case LOGIN: //用户登陆
            {
                ClientListUpdate(ADD, Msg.SourceUser, Msg.SourceNum);   //添加用户
                MsgTransmit(LOGIN, Msg.SourceNum, Msg); //广播登陆信息
                break;
            }
            case LOGOUT:    //用户登出
            {
                logout_fd = CList[Msg.SourceNum].socketfd;  //获取登出者的socketfd
                ClientListUpdate(DEL, Msg.SourceUser, Msg.SourceNum);   //删除用户
                MsgTransmit(LOGOUT, Msg.SourceNum, Msg);    //广播登出信息
                close(logout_fd);   //关闭对应的socket
                break;
            }
            case PUBLIC:    //有广播信息
            {
                MsgTransmit(PUBLIC, Msg.SourceNum, Msg);    //那就广播
                break;
            }
            case PRIVATE:   //有私信信息
            {
                MsgTransmit(PRIVATE, Msg.SourceNum, Msg);   //那就发送
                break;
            }
            default:    //无法识别信息类型
            {
                printf("\e[33m[Error] Data corrupted.\e[0m\n"); //那就报错
                break;
            }
        }
    }
    return NULL;
}